#include "../interface/C_gl_Device.h"

//////////////////////////////////////////////////////////////////////////
/// GL low level include
//////////////////////////////////////////////////////////////////////////
#include "../glCommon/glError.h"

#include "../interface/C_gl_ShaderResourceView.h"
#include "../interface/C_gl_RenderTargetView.h"
#include "../interface/C_gl_DepthStencilView.h"
#include "../interface/C_gl_UnorderedAccessView.h"

#ifdef _DEBUG
#define SAFE_NEW(p) { fprintf(stderr, "New returned %d at line %d in file %s\n", p, __LINE__, __FILE__); }
#else
#define SAFE_NEW(p)
#endif

u32	C_gl_Device::ComparisonToOpenGL(E_api_Comparison_Func func) const
{
	u32 gl;

	switch(func)
	{
	case E_api_Comparison_Always: gl = GL_ALWAYS; break;
	case E_api_Comparison_Equal: gl = GL_EQUAL; break;
	case E_api_Comparison_Greater: gl = GL_GREATER; break;
	case E_api_Comparison_Greater_Equal: gl = GL_GEQUAL; break;
	case E_api_Comparison_Less: gl = GL_LESS; break;
	case E_api_Comparison_Less_Equal: gl = GL_LEQUAL; break;
	case E_api_Comparison_Never: gl = GL_NEVER; break;
	case E_api_Comparison_Not_Equal: gl = GL_NOTEQUAL; break;
	default: gl = GL_LESS; break;
	}

	return gl;
}

u32	C_gl_Device::StencilopToOpenGL(E_api_Stencil_Op	op) const
{
	u32 gl;

	switch(op)
	{
	case E_api_Stencil_Op_Decr: gl = GL_DECR; break;
	case E_api_Stencil_Op_Decr_Sat: gl = GL_DECR_WRAP; break;
	case E_api_Stencil_Op_Incr: gl = GL_INCR; break;
	case E_api_Stencil_Op_Incr_Sat: gl = GL_INCR_WRAP; break;
	case E_api_Stencil_Op_Invert: gl = GL_INVERT; break;
	case E_api_Stencil_Op_Keep: gl = GL_KEEP; break;
	case E_api_Stencil_Op_Replace: gl = GL_REPLACE; break;
	case E_api_Stencil_Op_Zero: gl = GL_ZERO; break;
	default: gl = GL_ZERO; break;
	}

	return gl;
}

u32	C_gl_Device::FormatToOpenGLFormat(E_api_Format format) const
{
	u32 gl = 0;

	switch(format)
	{
	case E_api_Format_r8g8b8a8_typeless: 
	case E_api_Format_r8g8b8a8_unorm: 		
	case E_api_Format_r8g8b8a8_snorm: 	
		gl = GL_RGBA; break;

	case E_api_Format_r8g8b8a8_sint: 
	case E_api_Format_r8g8b8a8_uint: 
		gl = GL_RGBA_INTEGER_EXT;
		break;

	case E_api_Format_r32g32b32a32_typeless: 
	case E_api_Format_r32g32b32a32_float: 
	case E_api_Format_r32g32b32a32_sint: 
	case E_api_Format_r32g32b32a32_uint: 
		gl = GL_RGBA; break;

	case E_api_Format_r32_typeless: 
	case E_api_Format_r32_float: 
	case E_api_Format_r32_sint: 
	case E_api_Format_r32_uint: 
		gl = GL_LUMINANCE; break;

	case E_api_Format_d16_unorm: 
	case E_api_Format_d24_unorm: 
	case E_api_Format_d32_unorm: 
	case E_api_Format_d24_unorm_s8_uint: 
	case E_api_Format_d32_float_s8x24_uint:
	case E_api_Format_d32_float:
		gl = GL_DEPTH_COMPONENT; break;

	case E_api_Format_r32g32_typeless: 
	case E_api_Format_r32g32_float: 
	case E_api_Format_r32g32_sint: 
	case E_api_Format_r32g32_uint: 		
		gl = GL_LUMINANCE_ALPHA; break;

	default: gl = GL_RGBA; break;
	}

	return gl;
}

u32	C_gl_Device::FormatToOpenGLType(E_api_Format format) const
{
	u32 gl = 0;

	switch(format)
	{
	case E_api_Format_r8g8b8a8_uint:
	case E_api_Format_r8g8b8a8_unorm:
		gl = GL_UNSIGNED_BYTE; break;
	
	case E_api_Format_r8g8b8a8_sint:
	case E_api_Format_r8g8b8a8_snorm:
		gl = GL_BYTE; break;

	case E_api_Format_r32_sint: 	
	case E_api_Format_r32g32_sint: 
	case E_api_Format_r32g32b32_sint:
	case E_api_Format_r32g32b32a32_sint: 
		gl = GL_INT; break;

	case E_api_Format_r32_uint: 
	case E_api_Format_r32g32_uint: 
	case E_api_Format_r32g32b32_uint: 
	case E_api_Format_r32g32b32a32_uint: 
		gl = GL_UNSIGNED_INT; break;

	case E_api_Format_r16_sint: 	
	case E_api_Format_r16g16_sint: 
	case E_api_Format_r16g16b16a16_sint: 
		gl = GL_SHORT; break;

	case E_api_Format_r16_uint: 
	case E_api_Format_r16g16_uint: 
	case E_api_Format_r16g16b16a16_uint: 
		gl = GL_UNSIGNED_SHORT; break;

	case E_api_Format_r32_float:
	case E_api_Format_r32g32_float: 
	case E_api_Format_r32g32b32_float: 
	case E_api_Format_r32g32b32a32_float: 
		gl = GL_FLOAT; break;

	default: gl = GL_UNSIGNED_INT; break;
	}

	return gl;
}

u32	C_gl_Device::FormatToOpenGLInternal(E_api_Format format) const
{
	u32 gl = 0;
	switch(format)
	{
	case E_api_Format_r8g8b8a8_uint:
		gl = GL_RGBA8UI_EXT; break;

	case E_api_Format_r8g8b8a8_sint:
		gl = GL_RGBA8I_EXT; break;

	case E_api_Format_r8g8b8a8_unorm:
	case E_api_Format_r8g8b8a8_snorm:
		gl = GL_RGBA; break;

	case E_api_Format_r32g32b32a32_sint: 
		gl = GL_RGBA32I_EXT; break;

	case E_api_Format_r32g32b32a32_uint: 
		gl = GL_RGBA32UI_EXT; break;

	case E_api_Format_r16_sint: 
		gl = GL_LUMINANCE16I_EXT; break;

	case E_api_Format_r16g16_sint: 
		gl = GL_LUMINANCE_ALPHA16I_EXT;  break;

	case E_api_Format_r16g16b16a16_sint: 
		gl = GL_RGBA16I_EXT;  break;

	case E_api_Format_r16g16b16a16_unorm: 
	case E_api_Format_r16g16b16a16_snorm: 
		gl = GL_RGBA16;  break;
	
	case E_api_Format_r16g16b16a16_float: 
		gl = GL_RGBA16F_ARB; break;
		
	case E_api_Format_r16_uint: 
		gl = GL_LUMINANCE16UI_EXT;  break;

	case E_api_Format_r16g16_uint: 
		gl = GL_LUMINANCE_ALPHA16UI_EXT;  break;

	case E_api_Format_r16g16b16a16_uint: 
		gl = GL_RGBA16UI_EXT; break;

	case E_api_Format_r32_float:
		gl = GL_LUMINANCE32F_ARB; break;

	case E_api_Format_r32g32_float: 
		gl = GL_LUMINANCE_ALPHA32F_ARB;  break;

	case E_api_Format_r32g32b32_float: 
		gl = GL_RGB32F_ARB; break;

	case E_api_Format_r32g32b32a32_float: 
		gl = GL_RGBA32F_ARB; break;

	case E_api_Format_d16_unorm:
		gl = GL_DEPTH_COMPONENT16; break;

	case E_api_Format_d24_unorm:						
		gl = GL_DEPTH_COMPONENT24; break;

	case E_api_Format_d32_unorm:
		gl = GL_DEPTH_COMPONENT32; break;

	case E_api_Format_d24_unorm_s8_uint:
		gl = GL_DEPTH24_STENCIL8_EXT; break;

	case E_api_Format_d32_float_s8x24_uint:
		gl = GL_DEPTH32F_STENCIL8_NV; break;

	case E_api_Format_d32_float:
		gl = GL_DEPTH_COMPONENT32F_NV; break;

	default: gl = GL_RGBA; break;
	}

	return gl;
}

bool C_gl_Device::CreateBuffer(const S_api_BufferDesc *pDesc, const S_api_Subresource_Data *pInitialData, I_api_Buffer ** ppBuffer)
{
	*ppBuffer = new C_gl_Buffer(pDesc, pInitialData, this);

	SAFE_NEW(*ppBuffer);

	return glCheckError(__LINE__, "C_gl_Device.cpp");
}

bool C_gl_Device::CreateTexture2D(const S_api_Texture2D_Desc *pDesc, const S_api_Subresource_Data *pInitialData, I_api_Texture_2D **ppTexture2D)
{
	*ppTexture2D = (I_api_Texture_2D *) new C_gl_Texture_2D(pDesc, pInitialData, this);

	SAFE_NEW(*ppTexture2D);

	return *ppTexture2D != NULL;
}

bool C_gl_Device::CreateVertexShader(const void *pShaderBytecode, int BytecodeLength, I_api_VertexShader **ppVertexShader)
{
	int *pId = NULL;
	if (pShaderBytecode != NULL && BytecodeLength == sizeof(int))
		pId = (int *) pShaderBytecode;
	else return false;

	*ppVertexShader = new C_gl_VertexShader(*pId);

	return *ppVertexShader != NULL;
}

bool C_gl_Device::CreatePixelShader(const void *pShaderBytecode, int BytecodeLength, I_api_PixelShader **ppPixelShader)
{
	int *pId = NULL;
	if (pShaderBytecode != NULL && BytecodeLength == sizeof(int))
		pId = (int *) pShaderBytecode;
	else return false;

	*ppPixelShader = new C_gl_PixelShader(*pId);

	return ppPixelShader != NULL;
}

bool C_gl_Device::CreateProgram(I_api_VertexShader *pVertexShader, I_api_PixelShader *pPixelShader, I_api_Program ** ppProgram, I_api_Blob ** ppErrorMsg)
{
	*ppProgram = (I_api_Program *) new C_gl_Program(pVertexShader, pPixelShader, ppErrorMsg);

	return (ppErrorMsg != NULL) ? (*ppErrorMsg == NULL) : true;
}

bool C_gl_Device::CreateShaderResourceView(I_api_Resource * pResource, const S_api_ShaderResourceView_Desc *pDesc, I_api_ShaderResourceView **ppSRView)
{
	*ppSRView = (I_api_ShaderResourceView *) new C_gl_ShaderResourceView(pResource, pDesc, this);

	return *ppSRView != NULL;
}

bool C_gl_Device::CreateRenderTargetView(I_api_Resource * pResource, const S_api_RenderTargetView_Desc *pDesc, I_api_RenderTargetView **ppRTView)
{
	*ppRTView = (I_api_RenderTargetView *) new C_gl_RenderTargetView(pResource, pDesc, this);

	SAFE_NEW(*ppRTView);

	return *ppRTView != NULL;
}

bool C_gl_Device::CreateDepthStencilView(I_api_Resource * pResource, const S_api_DepthStencilView_Desc *pDesc, I_api_DepthStencilView **ppDepthStencilView)
{
	*ppDepthStencilView = (I_api_DepthStencilView *) new C_gl_DepthStencilView(pResource, pDesc, this);

	SAFE_NEW(*ppDepthStencilView);

	return *ppDepthStencilView != NULL;
}

bool C_gl_Device::CreateUnorderedAccessView(I_api_Resource * pResource, const S_api_UnorderedAccessView_Desc *pDesc, I_api_UnorderedAccessView **ppUnordererdAccessView)
{
	if (ppUnordererdAccessView)
	{
		*ppUnordererdAccessView = (I_api_UnorderedAccessView *) new C_gl_UnorderedAccessView(pResource, pDesc, this);

		SAFE_NEW(*ppUnordererdAccessView);

		return *ppUnordererdAccessView != NULL;
	}
	return false;
}


bool C_gl_Device::CreateFramebuffer(int nRenderTargetView, I_api_RenderTargetView ** pRenderTargetView, I_api_DepthStencilView * pDepthStencilView, I_api_Framebuffer ** ppFramebuffer)
{
	*ppFramebuffer = (I_api_Framebuffer *) new C_gl_Framebuffer(nRenderTargetView, pRenderTargetView, pDepthStencilView);

	SAFE_NEW(*ppFramebuffer);

	return *ppFramebuffer != NULL;
}

bool C_gl_Device::CreateRasterizerState(const S_api_Rasterizer_Desc * pDesc, I_api_RasterizerState **ppRasterizerState)
{
	*ppRasterizerState = new I_api_RasterizerState(pDesc, this);

	return	*ppRasterizerState != 0;
}

bool C_gl_Device::CreateDepthStencilState(const S_api_DepthStencilState * pDesc, I_api_DepthStencilState **ppDepthStencilState)
{
	*ppDepthStencilState = new I_api_DepthStencilState(pDesc, this);

	return	*ppDepthStencilState != 0;
}

void C_gl_Device::SetProgram(I_api_Program *pProgram)
{
	glUseProgram(*(unsigned int *)pProgram->Get());
}

void C_gl_Device::Flush()
{
	glFlush();
}

void C_gl_Device::IASetVertexBuffers(int StartSlot, int NumBuffers, I_api_Buffer **ppVertexBuffers, int *pStrides, int *pOffsets)
{
	for (int iElement = 0; iElement < NumBuffers; iElement++)
	{
	}
}

void C_gl_Device::Draw(int VertexCount, int StartVertexLocation)
{
}

void C_gl_Device::DrawIndexed(int IndexCount, int StartIndexLocation, int BaseVertexLocation)
{
}

void C_gl_Device::OMSetFramebuffer(I_api_Framebuffer *pFramebuffer)
{
	pFramebuffer->OMSetRenderTargets();
}

void C_gl_Device::OMSetDepthStencilState(I_api_DepthStencilState *pDepthStencilState, u32 StencilRef)
{
	S_api_DepthStencilState desc;
	pDepthStencilState->GetDesc(&desc);

	if (desc.DepthEnable)
	{
		glEnable(GL_DEPTH_TEST);

		glDepthFunc(ComparisonToOpenGL(desc.DepthFunc));

		GLboolean mask = desc.DepthWriteMask == E_api_Depth_Write_Mask_All ? true : false;
		glDepthMask(mask);
	}
	else
		glDisable(GL_DEPTH_TEST);

	if (desc.StencilEnable)
	{
		glEnable(GL_STENCIL_TEST);

		GLenum func = ComparisonToOpenGL(desc.FrontFace.StencilFunc); 
		glStencilFuncSeparate (GL_FRONT, func, StencilRef, desc.StencilReadMask);			

		GLenum sfail = StencilopToOpenGL(desc.FrontFace.StencilFailOp);
		GLenum zfail = StencilopToOpenGL(desc.FrontFace.StencilDepthFailOp);
		GLenum zpass = StencilopToOpenGL(desc.FrontFace.StencilPassOp);
		glStencilOpSeparate(GL_FRONT, sfail, zfail, zpass);

		func = ComparisonToOpenGL(desc.BackFace.StencilFunc); 
		glStencilFuncSeparate (GL_BACK, func, StencilRef, desc.StencilReadMask);			

		sfail = StencilopToOpenGL(desc.BackFace.StencilFailOp);
		zfail = StencilopToOpenGL(desc.BackFace.StencilDepthFailOp);
		zpass = StencilopToOpenGL(desc.BackFace.StencilPassOp);
		glStencilOpSeparate(GL_BACK, sfail, zfail, zpass);
		
		glStencilMask (desc.StencilWriteMask);
	}
	else
		glDisable(GL_STENCIL_TEST);
}

void C_gl_Device::RSSetState(I_api_RasterizerState *pRasterizerState)
{
	S_api_Rasterizer_Desc	desc;
	pRasterizerState->GetDesc(&desc);

	if (desc.CullMode != E_api_Cull_None)
	{
		GLenum cullmode = desc.CullMode == E_api_Cull_Front ? GL_FRONT : GL_BACK;
		glEnable(GL_CULL_FACE);
		glCullFace(cullmode);
	}
	else glDisable(GL_CULL_FACE);

	GLenum frontface = desc.FrontCounterClockwise ? GL_CCW : GL_CW;
	glFrontFace(frontface);

	GLenum fillmode = desc.FillMode == E_api_Fill_Solid ? GL_FILL : GL_LINE;
	glPolygonMode(GL_FRONT_AND_BACK, fillmode);

	if (desc.AntialiasedLineEnable) glEnable(GL_LINE_SMOOTH);
	else glDisable(GL_LINE_SMOOTH);

	GLenum polyoffsetmode = desc.FillMode == E_api_Fill_Solid ? GL_POLYGON_OFFSET_FILL : GL_POLYGON_OFFSET_LINE;
	if (desc.SlopeScaledDepthBias != 0.0f || desc.DepthBias != 0.0f)
	{
		glEnable(polyoffsetmode);
		glPolygonOffset(desc.SlopeScaledDepthBias, desc.DepthBias);
	}
	else
		glDisable(polyoffsetmode);
}

//////////////////////////////////////////////////////////////////////////
/// Clear calls
//////////////////////////////////////////////////////////////////////////
void C_gl_Device::ClearDepthStencilView(I_api_DepthStencilView *pDepthStencilView, u32 ClearFlags, float Depth, u8 Stencil) { /* not implemented */} 

void C_gl_Device::ClearRenderTargetView(I_api_RenderTargetView *pRenderTargetView, const float ColorRGBA[4]) { /* not implemented */} 

void C_gl_Device::ClearFramebuffer(I_api_Framebuffer *pFramebuffer, u32 ClearMask, const float Color[4], float Depth, u8 Stencil)
{
	GLbitfield mask = 0;
	mask  = ClearMask & E_api_Clear_Color ? GL_COLOR_BUFFER_BIT : 0;
	mask |= ClearMask & E_api_Clear_Depth ? GL_DEPTH_BUFFER_BIT : 0;
	mask |= ClearMask & E_api_Clear_Stencil ? GL_STENCIL_BUFFER_BIT : 0;

	glClearColor(Color[0], Color[1], Color[2], Color[3]);
	glClearDepth(Depth);
	glClearStencil(Stencil);

	glClear(mask);
}